package com.pig4cloud.pigx.common.security.grant.api;

import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
import com.pig4cloud.pigx.common.security.service.PigxCustomTokenServices;
import org.apache.logging.log4j.util.Strings;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * OpenApi令牌授予者
 *
 * @author hzq
 * @since 2021-09-14
 */
public class ApiTokenGranter extends AbstractTokenGranter {

    public static final String GRANT_TYPE = "api";
    public static final String CLIENT_ID = GRANT_TYPE;

    private final AuthenticationManager authenticationManager;

    public ApiTokenGranter(AuthenticationManager authenticationManager,
                           AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
                           OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    protected ApiTokenGranter(AuthenticationManager authenticationManager,
                              AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
                              OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        // 从当前请求头中获取AccessKey
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String accessKey = request.getHeader(SecurityConstants.X_API_KEY);
        if (Strings.isBlank(accessKey)) {
            throw new InvalidGrantException("Bad credentials [ params must be has api-access-key ]");
        }

        try {
            // 尝试直接获取token缓存，没有获取到时，继续进行创建token流程
            PigxCustomTokenServices tokenService = SpringContextHolder.getBean(PigxCustomTokenServices.class);
            return tokenService.loadAuthentication(accessKey);
        } catch (Throwable ignore) {
        }

        // 获取token
        Authentication auth = new ApiAuthenticationToken(accessKey, tokenRequest.getGrantType());
        ((AbstractAuthenticationToken) auth).setDetails(tokenRequest.getRequestParameters());

        try {
            // 通过authenticationManager调用provider，进行鉴权
            auth = authenticationManager.authenticate(auth);
        } catch (AccountStatusException | BadCredentialsException ase) {
            // covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }

        // 鉴权失败
        if (auth == null || !auth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate access key: " + accessKey);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, auth);
    }
}
